#include <utility>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sstream>
#include "tabla.h"
using namespace std;

/******************************************************** 
 Instancia del objeto TablaSimbolos. El resto de programas 
 utiliza esta instancia a través de la referencia externa 
 declarada en tabla.h
*********************************************************/
TablaSimbolos tabla;


/******************************************************** 
 Array para almacenar los nombres de los tipos manejados
 por nuestro compilador, que se corresponden en orden con
 la enumeración correspondiente definida en tabla.h
*********************************************************/
const static string nombreTipos[] = { 
  "int", "función", "void"
};

/******************************************************** 
 Array para almacenar los nombres de los tipos de 
 almacenamiento manejados, que se corresponden en orden con
 la enumeración correspondiente definida en tabla.h
*********************************************************/
const static string nombreAlm[]   = {
  "dinamico", "parámetro", "estático"
};

/******************************************************** 
 Implementación de la sobrecarga del operador << para
 Entrada, utilizado por la sobrecarga del operador <<
 para TablaSimbolos, definido más adelante.
*********************************************************/
std::ostream& operator<<(std::ostream& os, const Entrada& e)
{
  if (e.val == FUNCION) {
    os << e.nombre << " (función, dirección: '" << e.direccion << "')";
  } else {
    os << e.nombre << " (" << nombreTipos[e.val]
       << ", almacenamiento: " << nombreAlm[e.alm]
       << ", dirección: '" << e.direccion << "'"; 
    os << ")";
  }

  return os;
}

/******************************************************** 
 Implementación del constructor de TablaSimbolos.
 Inicializa la variable bDentroFuncion a false, pues al 
 principio del análisis de un programa en C, todavía no se
 ha abierto ninguna función.
*********************************************************/
TablaSimbolos::TablaSimbolos() 
  : bDentroFuncion(false)
{}

/******************************************************** 
 Implementación de abrirFuncion.
 Crea una entrada nueva correspondiente a una función en
 la tabla de símbolos.

 La inserta en la tabla de símbolos globales

 La direción de la función es su nombre.

 Modifica bDentroFuncion a true, e inicializa los
 deplazamientos de las variables dinámicas.
*********************************************************/
void TablaSimbolos::abrirFuncion(const std::string& nombre)
  throw (ExAmbitoNoValido, ExElementoYaExiste)
{
  Entrada nueva;
  nueva.val       = FUNCION;
  nueva.nombre    = nombre;
  nueva.direccion = nombre;

  if (bDentroFuncion) {
    throw ExAmbitoNoValido("Cambio de ámbito no válido: "
			   "ya estamos dentro de una función");
  }
  if (!globales.insert(make_pair(nombre, nueva)).second) {
    throw ExElementoYaExiste(nombre);
  }

  bDentroFuncion  = true;
  desplParametros = 8;
  desplLocales    = -4;
}

/******************************************************** 
 Implementación de cerrarFuncion.

 Chequea que no se pueda cerrar una función si no se está
 dentro de una función.

 Limpia las variables locales.
 
 Marca bDentroFuncion a false.
*********************************************************/
void TablaSimbolos::cerrarFuncion() 
  throw (ExAmbitoNoValido)
{
  if (!bDentroFuncion) {
    throw ExAmbitoNoValido("Cambio de ámbito no válido: "
			   "no estamos dentro de una función");
  }

  locales.clear();
  bDentroFuncion = false;
}


/******************************************************** 
 Implementación de getNumGlobales

 Devuelve la longitud de la tabla interna de simbolos locales.
*********************************************************/
int TablaSimbolos::getNumGlobales() const
{
  return globales.size();
}


/******************************************************** 
 Implementación de getNumLocales

 Recorre la lista de simbolos locales, y devuelve el numero
 de ellos que corresponden a variables dinámicas.
*********************************************************/
int TablaSimbolos::getNumLocales()  const
{
  int n = 0;
  for (TablaInterna::const_iterator it = locales.begin();
       it != locales.end(); ++it) {
    if (it->second.alm == DINAMICO) ++n;
  }

  return n;
}


/******************************************************** 
 Implementación de getNumParams

 Recorre la lista de simbolos locales, y devuelve el numero
 de ellos que corresponden a parámetros de la función.
*********************************************************/
int TablaSimbolos::getNumParams()  const
{
  int n = 0;
  for (TablaInterna::const_iterator it = locales.begin();
       it != locales.end(); ++it) {
    if (it->second.alm == PARAMETRO) ++n;
  }

  return n;
}

/******************************************************** 
 Implementación de agregar
*********************************************************/
void TablaSimbolos::agregar(const std::string& nombre, TipoValor val, 
			    TipoAlmacenamiento alm, int longitud)
  throw (ExElementoYaExiste, ExAmbitoNoValido)
{
  
  // Si ni es un parametro, y estamos dentro de una función, el 
  // almacenamiento es dinámico
  if(bDentroFuncion && alm != PARAMETRO) {
    alm = DINAMICO;
  }
 

  // Construimos la entrada
  Entrada e;
  e.nombre   = nombre;
  e.val      = val;
  e.alm = alm;
  e.longitud = longitud;

  if (!bDentroFuncion && (alm == DINAMICO || alm == PARAMETRO)) {
    throw ExAmbitoNoValido("No estas dentro de la funcion.");      
  }

  // Calculo de la dirección de esta entrada
  {
    ostringstream s;
    switch (alm) {
    case ESTATICO: 
      s << "$" << nombre;
      break;
    case DINAMICO: 
      s << desplLocales << "(%ebp)";
      desplLocales -= 4;
      break;
    case PARAMETRO:
    default: 
      s << desplParametros << "(%ebp)";
      desplParametros += 4;
      break;
    }
    e.direccion = s.str();
  }

  // Si estamos dentro de una función, insertamos en la lista de las locales.
  if (bDentroFuncion && locales.insert(make_pair(nombre, e)).second) {
    // Si no estamos dentro de una función, insertamos en las globales.
  } else if (!bDentroFuncion && globales.insert(make_pair(nombre, e)).second) {
    
  } else {
    // Caso de que hayan fallado alguno de los anteriores, eso significa que 
    // el elemento ya existía en su ámbito.
    throw ExElementoYaExiste(nombre);
  }

}

/******************************************************** 
 Implementación de buscar
 Se busca la entrada en la tabla de símbolos primero en
 la tabla local, y si no se encontrara, en la tabla global.
 Caso de no encontrarse en ninguna, se dispara una exepción
*********************************************************/
const Entrada& TablaSimbolos::buscar(const std::string& nombre) const
  throw (ExElementoNoExiste)
{
  TablaInterna::const_iterator 
    itLocales  = locales.find(nombre),
    itGlobales = globales.find(nombre);

  if (itLocales != locales.end()) {
    return itLocales->second;
  }
  else if (itGlobales != globales.end()) {
    return itGlobales->second;
  }
  else {
    throw ExElementoNoExiste(nombre);
  }  
}


/******************************************************** 
 Implementación de generar
 Generamos la sección de datos del programa, recorriendo 
 la tabla de globales, y haciendo una entrada por cada 
 tipo entero que encontremos.
*********************************************************/
void TablaSimbolos::generar() const
{
  cout << ".section .data" << endl;
  
  for (TablaInterna::const_iterator it = globales.begin();
       it != globales.end(); ++it) 
    {
      switch (it->second.val) {
      case ENTERO:
	      cout << ".comm " << it->first << ", 4, 4" << endl;
	      break;

      default:
			break;
      }
    }
}


std::ostream& operator<<(std::ostream& os, const TablaSimbolos& t)
{
  if (t.bDentroFuncion) {
    os << "-- LOCALES --" << endl;
    for (TablaSimbolos::TablaInterna::const_iterator it = t.locales.begin(); 
	 it != t.locales.end(); ++it) 
      {
	os << it->second << endl;
      }
  }

  os << "-- GLOBALES -- " << endl;
  for (TablaSimbolos::TablaInterna::const_iterator it = t.globales.begin(); 
       it != t.globales.end(); ++it) 
    {
      os << it->second << endl;
    }

  return os;
}


